/*

rendering of transimg layers into a HxWx3 uint8 image

FORMAT:       r = renderc(tio)

Input fields:

      tio         1x1 struct of transimg content

Output fields:

      r           HxWx3 uint8 image

% Version:  v0.8a
% Build:    9112020
% Date:     Nov-20 2009, 8:16 PM CET
% Author:   Jochen Weber, SCAN Unit, Columbia University, NYC, NY, USA
% URL/Info: http://wiki.brainvoyager.com/BVQXtools

*/

#include "mex.h"
#include "matrix.h"
#include "math.h"
#include <stdio.h>

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    
    /* dimensions */
    int h = 0, w = 0, hw = 0, hw3 = 0, nl = 0, lc = 0, 
        lfield = -1, pfield = -1, afield = -1,
        numa = 0, ndp = 0, nda = 0, pc = 0;
    int od[3] = {0, 0, 3};
    const mxArray *layer = NULL, *inpp = NULL, *inpa = NULL;
    
    /* pointers */
    const double *ap = NULL;
    const unsigned char *pp = NULL, *pp2 = NULL, *pp3 = NULL;
    unsigned char *opp = NULL;
    
    /* background color */
    float bg1 = 0.0, bg2 = 0.0, bg3 = 0.0, al = 1.0, alm = 0.0;
    
    /* buffer */
    float *buffer = NULL, *bf1 = NULL, *bf2 = NULL, *bf3 = NULL;
    
    /* variable output string */
    char vstr[256];

    /* check number, type, fields of in/out arguments */
	if ((nrhs != 1) || 
        (nlhs > 1))
		mexErrMsgTxt("Bad number of input/output arguments.");
    if (!mxIsStruct(*prhs))
        mexErrMsgTxt("Input must be of type struct.");
    lfield = mxGetFieldNumber(*prhs, "Layer");
    if ((mxGetFieldNumber(*prhs, "Background") < 0) ||
        (mxGetFieldNumber(*prhs, "Height") < 0) ||
        (lfield < 0) ||
        (mxGetFieldNumber(*prhs, "Width") < 0))
        mexErrMsgTxt("Required field missing.");
    layer = mxGetFieldByNumber(*prhs, 0, lfield);
    if (!mxIsStruct(layer))
        mexErrMsgTxt(".Layer field must be of type struct.");
    pfield = mxGetFieldNumber(layer, "Pixel");
    afield = mxGetFieldNumber(layer, "Alpha");
    if ((pfield < 0) ||
        (afield < 0))
        mexErrMsgTxt(".Layer field must have subfields .Pixel and .Alpha.");
    nl = mxGetNumberOfElements(layer);
    inpa = (const mxArray *) mxGetField(*prhs, 0, "Background");
    if (mxGetNumberOfElements(inpa) != 3)
        mexErrMsgTxt("Invalid .Background field.");
    if (!mxIsUint8(inpa))
        mexErrMsgTxt("Invalid .Background field datatype.");
    pp = (const unsigned char *) mxGetData(inpa);
    bg1 = (float) *pp++;
    bg2 = (float) *pp++;
    bg3 = (float) *pp;
    inpa = (const mxArray *) mxGetField(*prhs, 0, "Height");
    if (mxGetNumberOfElements(inpa) != 1)
        mexErrMsgTxt("Invalid .Height field.");
    if (!mxIsDouble(inpa))
        mexErrMsgTxt("Invalid .Height field datatype.");
    ap = (const double *) mxGetData(inpa);
    h = (int) *ap;
    inpa = (const mxArray *) mxGetField(*prhs, 0, "Width");
    if (mxGetNumberOfElements(inpa) != 1)
        mexErrMsgTxt("Invalid .Width field.");
    if (!mxIsDouble(inpa))
        mexErrMsgTxt("Invalid .Width field datatype.");
    ap = (const double *) mxGetData(inpa);
    w = (int) *ap;
    *od = h;
    od[1] = w;
    hw = h * w;
    hw3 = 3 * hw;
    
    /* create output */
    *plhs = (mxArray *) mxCreateNumericArray(3, od, mxUINT8_CLASS, mxREAL);
    if (*plhs == NULL)
        mexErrMsgTxt("Error creating output mxArray.");
    opp = (unsigned char *) mxGetData(*plhs);
    if (opp == NULL)
        mexErrMsgTxt("Error getting data pointer to output mxArray.");
    
    /* create float representation of output for temp computation */
    buffer = (float *) mxCalloc(hw3, sizeof(float));
    if (buffer == NULL) {
        mxDestroyArray(*plhs);
        mexErrMsgTxt("Error allocating temp buffer.");
    }
    
    /* set background color */
    for (bf1 = buffer, bf2 = &bf1[hw], bf3 = &bf2[hw], pc = hw; pc > 0; --pc) {
        *bf1++ = bg1;
        *bf2++ = bg2;
        *bf3++ = bg3;
    }
    
    /* parse layers */
    for (lc = 0; lc < nl; ++lc) {
        
        /* get pixel and alpha information */
        inpp = mxGetFieldByNumber(layer, lc, pfield);
        inpa = mxGetFieldByNumber(layer, lc, afield);
        ndp = mxGetNumberOfDimensions(inpp);
        nda = mxGetNumberOfDimensions(inpa);
        if ((!mxIsUint8(inpp)) ||
            (!mxIsDouble(inpa)) ||
            (ndp > 3) ||
            (nda > 2)) {
            mxFree(buffer);
            mxDestroyArray(*plhs);
            mexErrMsgTxt("Invalid .Pixel/.Alpha field types/dims.");
        }
        ndp = mxGetNumberOfElements(inpp);
        nda = mxGetNumberOfElements(inpa);
        if (((ndp != hw) &&
             (ndp != hw3)) ||
            ((nda != 1) &&
             (nda != hw))) {
            mxFree(buffer);
            mxDestroyArray(*plhs);
            mexErrMsgTxt("Invalid .Pixel/.Alpha field sizes.");
        }
        pp = (const unsigned char *) mxGetData(inpp);
        ap = (const double *) mxGetData(inpa);
        
        /* depending on size of alpha */
        if (nda == 1) {
            
            /* get alpha value */
            al = (float) *ap;
            alm = 1.0 - al;
            
            /* depending on size of pixels */
            if (ndp == hw3) {
                
                /* iterate over pixel */
                for (bf1 = buffer, bf2 = &bf1[hw], bf3 = &bf2[hw], pp2 = &pp[hw], pp3 = &pp2[hw], pc = hw; pc > 0; --pc) {
                    *bf1++ = alm * *bf1 + al * *pp++;
                    *bf2++ = alm * *bf2 + al * *pp2++;
                    *bf3++ = alm * *bf3 + al * *pp3++;
                }
            } else {
                for (bf1 = buffer, bf2 = &bf1[hw], bf3 = &bf2[hw], pc = hw; pc > 0; --pc) {
                    *bf1++ = alm * *bf1 + al * *pp;
                    *bf2++ = alm * *bf2 + al * *pp;
                    *bf3++ = alm * *bf3 + al * *pp++;
                }
            }
            
        } else {
            
            if (ndp == hw3) {
                for (bf1 = buffer, bf2 = &bf1[hw], bf3 = &bf2[hw], pp2 = &pp[hw], pp3 = &pp2[hw], pc = hw; pc > 0; --pc) {
                    al = (float) *ap++;
                    alm = 1.0 - al;
                    *bf1++ = alm * *bf1 + al * *pp++;
                    *bf2++ = alm * *bf2 + al * *pp2++;
                    *bf3++ = alm * *bf3 + al * *pp3++;
                }
            } else {
                for (bf1 = buffer, bf2 = &bf1[hw], bf3 = &bf2[hw], pc = hw; pc > 0; --pc) {
                    al = (float) *ap++;
                    alm = 1.0 - al;
                    *bf1++ = alm * *bf1 + al * *pp;
                    *bf2++ = alm * *bf2 + al * *pp;
                    *bf3++ = alm * *bf3 + al * *pp++;
                }
            }
        }
    }
    
    /* put into output */
    for (bf1 = buffer, pc = hw3; pc > 0; --pc)
        *opp++ = (unsigned char) (((float) 0.5) + *bf1++);
    
    /* free buffer */
    mxFree(buffer);
    
}
